本文章原始创作地址:https://blog.csdn.net/qq_20264891/article/details/91357985
如您有其他建议,可以通过左上角的 QQ 发起回话,或者在 https://zq-jhon.github.io/about/ 中添加我的微信
起因
一直享受着 Anuglar 和 Nest 的红利,上来就是 @Component(...)
或者 @Controller(...)
,自己却没有实际的探究过背后的原理。于是今天想好好总结一下,沉淀沉淀。
前置条件(es5 原理)
之前看过红宝书,第六章提到过,js 对象的属性有几个特性:
- [[configurable]] 是否可配置
- [[enumerable]] 是否可枚举
- [[writeble]] 是否可修改值
- [[value]] 写入的值是啥
四个配置项都为 boolean 类型。
这四个配置联合起来有一个名字,叫做对象属性的描述符(descriptor)
其中,writeble 和 value 还有另外一个名字, setter
和 getter
访问器)。
上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const obj = { }; Object.defineProperty(obj,'a', { value: 1, writeble: false, }); console.log(obj); console.log(obj.a) obj.a = 3; console.log(obj.a)
const d = {}; Object.defineProperty(d , 'name' { get: function() {return 1}, set: function(value) {return false} });
console.log(d) d.name; d.name = 3; d.name;
|
我们发现,配置了可写入项为 false 时,我们就无法去修改对象属性的值了,有点像冻结的意思。刚好,JS 有个 Object.freeze()
, 来看一下
1 2 3 4
| const c = {name: 1}; Object.freeze(c); c.name = 3; console.log(c)
|
发现和我们自己去配置 writeble: false
效果相同。不信?来验证一下:
1 2 3 4 5 6 7 8 9 10
| Object.getOwnPropertyDescriptor(c);
{ name: { configurable: false enumerable: true value: 1 writable: false } }
|
ES6 还要这么写吗?
不用。直接用装饰器 decorator
来写。
第一种,直接装饰 class,
作用: 给类增加动态属性,该动态属性并不会被继承,只能作为 被装饰类 的 静态属性。
注意: 给类添加静态属性的这种行为,是在 编译时 发生的!所以说:
装饰器的本质就是编译时运行的函数
1 2 3 4 5 6 7 8 9 10 11 12
| function addFlag(object) { object.flag = true; }
@addFlag class Foo(){} Foo.flag
const f1 = new Foo(); f1.flag
|
第二种,装饰属性
装饰器会在 Object.defineProperty
之前执行,也就是拦截默认的访问修饰符。
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13
| function nameEqual3(object, key, descriptor: PropertyDescriptor) { descriptor.value = 3; descriptor.writable = false; } class Person {
@nameEqual3 name() { } }
const p = new Person(); console.log(p.name);
|
可见其效果。
也支持传参,如下代码所示,请仔细阅读注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| function sign(id) { return function (target, name, descriptor) {
const oldValue = descriptor.value;
descriptor.value = function (...args) { console.log(`args =>`, args); console.log(`标记 ${id}`); return oldValue.apply(this, args); };
return descriptor; } }
class Person { @sign(1) method(a, b) { return a + b; } }
const p1 = new Person(); p1.method(2, 4);
args => [3,4] 标记 1
|
第三种,装饰器的高级用法(链式调用, combine 以及 mixin)
1.链式(连续)
首先来看链式(连续)调用,这次多加一个装饰器,并且继续通过打印的方式来查看下调用的顺序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
function mark(id) { return (obj, target, descriptor) => { const old = descriptor.value; console.log(id); return descriptor.value = () => old.apply(this, id); } }
class Person {
@mark(1) @mark(2) method() { } }
const p1 = new Person();
p1.method();
2 1
|
咦?明明 @mark(1)
在 @mark(2)
之前调用的啊,为什么 2 比 1 先执行了呢?
让我们打开 如下地址,跟着我一起分析:
Type Script - Play ground%20%7B%0D%0A%20%20%2F%2F%20%E7%9C%9F%E6%AD%A3%E7%9A%84%E8%A3%85%E9%A5%B0%E5%99%A8%E5%87%BD%E6%95%B0%E4%BB%A5%E9%97%AD%E5%8C%85%E5%BD%A2%E5%BC%8F%E8%BF%94%E5%9B%9E%0D%0A%20%20return%20(obj%2C%20target%2C%20descriptor)%20%3D%3E%20%7B%0D%0A%20%20%20%20%2F%2F%20%E4%B8%8D%E7%A0%B4%E5%9D%8F%E5%8E%9F%20getter%20%E5%87%BD%E6%95%B0%0D%0A%20%20%20%20const%20old%20%3D%20descriptor.value%3B%0D%0A%20%20%20%20console.log(id)%3B%0D%0A%20%20%20%20return%20descriptor.value%20%3D%20()%20%3D%3E%20old.apply(this%2C%20id)%3B%0D%0A%20%20%7D%0D%0A%7D%0D%0A%0D%0A%0D%0A%0D%0Aclass%20Person%20%7B%0D%0A%0D%0A%20%20%40mark(1)%0D%0A%20%20%40mark(2)%0D%0A%20%20method()%20%7B%20%7D%0D%0A%7D%0D%0A%0D%0A%0D%0Aconst%20p1%20%3D%20new%20Person()%3B%0D%0A%0D%0Ap1.method()%3B%0D%0A%0D%0A%2F%2F%20%E8%BE%93%E5%87%BA%EF%BC%9A%0D%0A2%20%0D%0A1)
来看右边编译后的 javascript 代码,只看 var decorator 被编译成了啥,下面的不用看,跟源码差不多。请仔细阅读注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; };
function mark(id) { var _this = this; return function (obj, target, descriptor) { var old = descriptor.value; console.log(id); return descriptor.value = function () { return old.apply(_this, id); }; }; } var Person = (function () { function Person() { } Person.prototype.method = function () { }; __decorate([ mark(1), mark(2) ], Person.prototype, "method", null); return Person; }()); var p1 = new Person(); p1.method();
|
上面啰里啰唆的注释是啥意思呢?
翻译成人话: 装饰器的执行顺序是个 栈, 后进先出。像极了… 爱情?不,像极了 洋葱模型。
2. combine (合并)
合并指的是装饰器装饰某个类的属性的时候,同时应用多个装饰器的模式。(要跟下面的 @mixin
)区分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| function eatApple(count) { return (obj,target,descriptor) => { const old = descriptor.value; console.log(`吃了 ${count} 个 苹果`); return old.apply(this); } }
function runMeter(long) { return (obj,target,descriptor) => { const old = descriptor.value; console.log(`跑了 ${long} 米`); return old.apply(this); } }
function combine(...descriptors) { return (obj, target, descriptor) => descriptors.forEach(d => d.apply(this, [obj, target, descriptor])); }
class Person {
@combine(eatApple(1), runMeter(9)) method() { } }
const p1 = new Person();
p1.method();
吃了一个苹果 跑了 9 米
|
可见,在 @combine()
中传入的参数顺序,竟然跟最终的顺序 是一样的,咦?不是洋葱吗?这压根不是栈啊!
脑子里回想一下刚才解析源码的过程,我再次望向了这次的源码:
1 2 3 4 5 6 7 8
| var Person = (function () { function Person() { } Person.prototype.method = function () { }; __decorate([ combine(eatApple(1), runMeter(9)) ], Person.prototype, "method", null); return Person;
|
显而易见,这两个函数,直接是作为结果被传进去的,相当于栈里面只有 mixin
一个函数,无所谓是栈或者队列了,反正两个函数都在我内部执行,我让他怎么执行就怎么执行,为所欲为。所以这里的输出结果是同步的,完全就是因为栈里只有一个 member。
不信验证一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| function eatApple(count) { return (obj,target,descriptor) => { const old = descriptor.value; console.log(`吃了 ${count} 个 苹果`); return old.apply(this); } }
function runMeter(long) { return (obj,target,descriptor) => { const old = descriptor.value; console.log(`跑了 ${long} 米`); return old.apply(this); } }
function combine(...descriptors) { return (obj, target, descriptor) => descriptors.forEach(d => d.apply(this, [obj, target, descriptor])); }
class Person {
@combine(eatApple(1), runMeter(9)) @combine(eatApple(5),runMeter(100)) method() { } }
const p1 = new Person();
p1.method();
吃了 5 个 苹果 跑了 100 米 吃了 1 个 苹果 跑了 9 米
|
3. mixin (混合)
mixin 意为在一个对象之中混入另外一个对象的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function mixins(...list) { return function (target) { Object.assign(target.prototype, ...list); }; } const Foo = { foo() { console.log('foo') } };
@mixins(Foo) class MyClass {}
let obj = new MyClass(); obj.foo()
|
😘 觉得文章有用?点击下方打赏,鼓励作者更好的写作!